#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/poll.h>

#define DEVICE_NAME "chardev"
#define BUFFER_SIZE 1024

static dev_t dev_num;
static struct cdev cdev;
static char *buffer;
wait_queue_head_t read_queue;
static int data_read = 1;

static int chardev_open(struct inode *inode, struct file *filp)
{
	filp->private_data = buffer;
	return 0;
}

static int chardev_release(struct inode *inode, struct file *filp)
{
	return 0;
}

static ssize_t chardev_read(struct file *filp, char __user * buf, size_t count,
			    loff_t * f_pos)
{
	int len = strlen(filp->private_data);
	if (len > count)
		len = count;
	if (copy_to_user(buf, filp->private_data, len))
		return -EFAULT;
	data_read = 1;
	return len;
}

static ssize_t chardev_write(struct file *filp, const char __user * buf,
			     size_t count, loff_t * f_pos)
{
	if (count > BUFFER_SIZE)
		count = BUFFER_SIZE;
	if (copy_from_user(filp->private_data, buf, count))
		return -EFAULT;
	*(char *)(filp->private_data+count) = '\0';
	data_read = 0;
	wake_up_interruptible(&read_queue);
	return count;
}

static unsigned int chardev_poll(struct file *filp, poll_table * wait)
{
	unsigned int mask = 0;
	poll_wait(filp, &read_queue, wait);
	if (!data_read && strlen(filp->private_data) > 0)
		mask |= POLLIN | POLLRDNORM;

    pr_info("poll return mask=%d\n",mask);
	return mask;
}

static const struct file_operations chardev_fops = {
	.owner = THIS_MODULE,
	.open = chardev_open,
	.release = chardev_release,
	.read = chardev_read,
	.write = chardev_write,
	.poll = chardev_poll,
};

static int __init chardev_init(void)
{
	int ret;
	buffer = kmalloc(BUFFER_SIZE, GFP_KERNEL);
	if (!buffer) {
		ret = -ENOMEM;
		goto out;
	}
	ret = alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);
	if (ret < 0) {
		kfree(buffer);
		goto out;
	}
	cdev_init(&cdev, &chardev_fops);
	cdev.owner = THIS_MODULE;
	ret = cdev_add(&cdev, dev_num, 1);
	if (ret < 0) {
		unregister_chrdev_region(dev_num, 1);
		kfree(buffer);
		goto out;
	}
	init_waitqueue_head(&read_queue);
	printk(KERN_INFO "Character device %s initialized\n", DEVICE_NAME);
	return 0;
 out:
	return ret;
}

static void __exit chardev_exit(void)
{
	cdev_del(&cdev);
	unregister_chrdev_region(dev_num, 1);
	kfree(buffer);
	printk(KERN_INFO "Character device %s removed\n", DEVICE_NAME);
}

module_init(chardev_init);
module_exit(chardev_exit);
MODULE_LICENSE("GPL");
